#pragma once

#include <QObject>
#include <QMetaType>
#include <QMetaEnum>
#include <QFileInfo>
#include <QJsonObject>
#include <QJsonArray>
#include <QString>
#include <QDebug>

#if defined(Q_OS_LINUX) || defined(Q_CC_MINGW)
#include <unistd.h>
#endif

#if defined(Q_OS_WIN) || defined(Q_CC_MINGW)
#include <minwindef.h>
#endif

// Debug functions caller stack tracker
#ifndef FUNCTION_TRACE
#  if defined (DEBUG_FTRACE)
#    if defined (DEBUG_FTRACE_FILE_FUNC)
#      define FUNCTION_TRACE    qDebug().noquote() << QString::asprintf("[%s:%d]<%s>", __FILE__, __LINE__, __FUNCTION__)
#    else
#      define FUNCTION_TRACE    qDebug().noquote() << QString::asprintf("<%s>", __FUNCTION__)
#    endif
#  else
#    define FUNCTION_TRACE      QT_NO_QDEBUG_MACRO()
#  endif
#endif

// Object safe deleter
#ifndef SAFE_DELETE
#define SAFE_DELETE(x)          if (x) { delete x; x = nullptr; }
#endif
#ifndef SAFE_DELETE_LATER
#define SAFE_DELETE_LATER(x)    if (x) { x->deleteLater(); x = nullptr; }
#endif

// Unit Convert
#ifndef UNIT_CONVERT
#define UNIT_CONVERT
#define UNIT_KB             (1024)
#define UNIT_MB             (UNIT_KB * 1024)
#define UNIT_GB             (UNIT_MB * 1024)
#define UNIT_TB             (UNIT_GB * 1024)
#define UNIT_TO_KB(x)       (x / UNIT_KB)
#define UNIT_TO_MB(x)       (x / UNIT_MB)
#define UNIT_TO_GB(x)       (x / UNIT_GB)
#define UNIT_TO_TB(x)       (x / UNIT_TB)
#endif

// Qt custom meta type register
#ifndef RegisterMetaTypes
#define RegisterMetaTypes(X) { \
    qRegisterMetaType<X>(#X); \
    qRegisterMetaType<X>(#X"&"); \
    qRegisterMetaType< QVector<X> >("QVector<"#X">"); \
    qRegisterMetaType< QVector<X> >("QVector<"#X">&"); \
}
#endif

#ifndef RegisterMetaTypeStreamOperators
#define RegisterMetaTypeStreamOperators(X) { \
    qRegisterMetaTypeStreamOperators<X>(#X); \
    qRegisterMetaTypeStreamOperators<X>(#X"&"); \
    qRegisterMetaTypeStreamOperators< QVector<X> >("QVector<"#X">"); \
    qRegisterMetaTypeStreamOperators< QVector<X> >("QVector<"#X">&"); \
}
#endif

typedef void (*SignalHandlerCallback)(int);

namespace EnumUtils {
// String --> Enum
template <typename T, typename std::enable_if<std::is_enum<T>::value, bool>::type = true>
static T Convert(const char* t) {
    Q_STATIC_ASSERT_X(QtPrivate::IsQEnumHelper<T>::Value, "Type is not a enumerator or not declared with Q_ENUM, Q_ENUM_NS, Q_FLAG or Q_FLAG_NS");
    QMetaEnum convertor = QMetaEnum::fromType<T>();
    bool success = false;
    int result = convertor.keyToValue(t, &success);
    if (!success) {
        result = -1;
    }
    return static_cast<T>(result);
}
template <typename T, typename std::enable_if<std::is_enum<T>::value, bool>::type = true>
static T Convert(const QString &t) {
    Q_STATIC_ASSERT_X(QtPrivate::IsQEnumHelper<T>::Value, "Type is not a enumerator or not declared with Q_ENUM, Q_ENUM_NS, Q_FLAG or Q_FLAG_NS");
    QMetaEnum convertor = QMetaEnum::fromType<T>();
    bool success = false;
    int result = convertor.keyToValue(t.toUtf8(), &success);
    if (!success) {
        result = -1;
    }
    return static_cast<T>(result);
}
// Enum --> String
template <typename T, typename std::enable_if<std::is_enum<T>::value, bool>::type = true>
static QByteArray Convert(T t) {
    Q_STATIC_ASSERT_X(QtPrivate::IsQEnumHelper<T>::Value, "Type is not a enumerator or not declared with Q_ENUM, Q_ENUM_NS, Q_FLAG or Q_FLAG_NS");
    QMetaEnum convertor = QMetaEnum::fromType<T>();
    return convertor.valueToKey(static_cast<int>(t));
}
} // EnumUtils

namespace JsonUtils {
// Getter: Numbers
template <typename T, typename std::enable_if<std::is_arithmetic<T>::value, bool>::type = true>
T Get(const QJsonObject &obj, const QString &key, T default_value = 0) {
    if (std::is_same<short, T>::value) return (obj.contains(key) ? obj.value(key).toString().toShort() : default_value);
    if (std::is_same<ushort, T>::value) return (obj.contains(key) ? obj.value(key).toString().toUShort() : default_value);
    if (std::is_same<int, T>::value) return (obj.contains(key) ? obj.value(key).toString().toInt() : default_value);
    if (std::is_same<uint, T>::value) return (obj.contains(key) ? obj.value(key).toString().toUInt() : default_value);
    if (std::is_same<long, T>::value) return (obj.contains(key) ? obj.value(key).toString().toLong() : default_value);
    if (std::is_same<ulong, T>::value) return (obj.contains(key) ? obj.value(key).toString().toULong() : default_value);
    if (std::is_same<qlonglong, T>::value) return (obj.contains(key) ? obj.value(key).toString().toLongLong() : default_value);
    if (std::is_same<qulonglong, T>::value) return (obj.contains(key) ? obj.value(key).toString().toULongLong() : default_value);
    if (std::is_same<float, T>::value) return (obj.contains(key) ? obj.value(key).toString().toFloat() : default_value);
    if (std::is_same<double, T>::value) return (obj.contains(key) ? obj.value(key).toString().toDouble() : default_value);
    if (std::is_same<bool, T>::value) return (obj.contains(key) ? obj.value(key).toBool() : default_value);
    return default_value;
}
// Getter: QString
template <typename T, typename std::enable_if<std::is_same<QString, T>::value, bool>::type = true>
T Get(const QJsonObject &obj, const QString &key, T default_value = QString()) {
    return (obj.contains(key) ? obj.value(key).toString() : default_value);
}
// Getter: QVector<Numbers>
template <typename T, typename std::enable_if<std::is_arithmetic<T>::value, bool>::type = true>
QVector<T> Get(const QJsonObject &obj, const QString &key, QVector<T> default_value = QVector<T>()) {
    if (obj.contains(key)) {
        QVector<T> value;
        QJsonValue val = obj.value(key);
        if (val.isObject()) {
            if (std::is_same<short, T>::value)      value.append(val.toString().toShort());
            if (std::is_same<ushort, T>::value)     value.append(val.toString().toUShort());
            if (std::is_same<int, T>::value)        value.append(val.toString().toInt());
            if (std::is_same<uint, T>::value)       value.append(val.toString().toUInt());
            if (std::is_same<long, T>::value)       value.append(val.toString().toLong());
            if (std::is_same<ulong, T>::value)      value.append(val.toString().toULong());
            if (std::is_same<qlonglong, T>::value)  value.append(val.toString().toLongLong());
            if (std::is_same<qulonglong, T>::value) value.append(val.toString().toULongLong());
            if (std::is_same<float, T>::value)      value.append(val.toString().toFloat());
            if (std::is_same<double, T>::value)     value.append(val.toString().toDouble());
            if (std::is_same<bool, T>::value)       value.append(val.toBool());
        } else if (val.isArray()) {
            QJsonArray array = val.toArray();
            for (const QJsonValue &item : qAsConst(array)) {
                if (item.isObject()) {
                    if (std::is_same<short, T>::value)      value.append(item.toString().toShort());
                    if (std::is_same<ushort, T>::value)     value.append(item.toString().toUShort());
                    if (std::is_same<int, T>::value)        value.append(item.toString().toInt());
                    if (std::is_same<uint, T>::value)       value.append(item.toString().toUInt());
                    if (std::is_same<long, T>::value)       value.append(item.toString().toLong());
                    if (std::is_same<ulong, T>::value)      value.append(item.toString().toULong());
                    if (std::is_same<qlonglong, T>::value)  value.append(item.toString().toLongLong());
                    if (std::is_same<qulonglong, T>::value) value.append(item.toString().toULongLong());
                    if (std::is_same<float, T>::value)      value.append(item.toString().toFloat());
                    if (std::is_same<double, T>::value)     value.append(item.toString().toDouble());
                    if (std::is_same<bool, T>::value)       value.append(item.toBool());
                }
            }
        } else {
            value.clear();
        }
        return value;
    }
    return default_value;
}
// Getter: QVector<QString>
template <typename T, typename std::enable_if<std::is_same<QString, T>::value, bool>::type = true>
QVector<T> Get(const QJsonObject &obj, const QString &key, QVector<T> default_value = QVector<T>()) {
    if (obj.contains(key)) {
        QVector<T> value;
        QJsonValue val = obj.value(key);
        if (val.isObject()) {
            value.append(val.toString());
        } else if (val.isArray()) {
            QJsonArray array = val.toArray();
            for (const QJsonValue &item : qAsConst(array)) {
                if (item.isObject()) {
                    value.append(item.toString());
                }
            }
        } else {
            value.clear();
        }
        return value;
    }
    return default_value;
}
// Getter: QList<Numbers>
template <typename T, typename std::enable_if<std::is_arithmetic<T>::value, bool>::type = true>
QList<T> Get(const QJsonObject &obj, const QString &key, QList<T> default_value = QList<T>()) {
    if (obj.contains(key)) {
        QList<T> value;
        QJsonValue val = obj.value(key);
        if (val.isObject()) {
            if (std::is_same<short, T>::value)      value.append(val.toString().toShort());
            if (std::is_same<ushort, T>::value)     value.append(val.toString().toUShort());
            if (std::is_same<int, T>::value)        value.append(val.toString().toInt());
            if (std::is_same<uint, T>::value)       value.append(val.toString().toUInt());
            if (std::is_same<long, T>::value)       value.append(val.toString().toLong());
            if (std::is_same<ulong, T>::value)      value.append(val.toString().toULong());
            if (std::is_same<qlonglong, T>::value)  value.append(val.toString().toLongLong());
            if (std::is_same<qulonglong, T>::value) value.append(val.toString().toULongLong());
            if (std::is_same<float, T>::value)      value.append(val.toString().toFloat());
            if (std::is_same<double, T>::value)     value.append(val.toString().toDouble());
            if (std::is_same<bool, T>::value)       value.append(val.toBool());
        } else if (val.isArray()) {
            QJsonArray array = val.toArray();
            for (const QJsonValue &item : qAsConst(array)) {
                if (item.isObject()) {
                    if (std::is_same<short, T>::value)      value.append(item.toString().toShort());
                    if (std::is_same<ushort, T>::value)     value.append(item.toString().toUShort());
                    if (std::is_same<int, T>::value)        value.append(item.toString().toInt());
                    if (std::is_same<uint, T>::value)       value.append(item.toString().toUInt());
                    if (std::is_same<long, T>::value)       value.append(item.toString().toLong());
                    if (std::is_same<ulong, T>::value)      value.append(item.toString().toULong());
                    if (std::is_same<qlonglong, T>::value)  value.append(item.toString().toLongLong());
                    if (std::is_same<qulonglong, T>::value) value.append(item.toString().toULongLong());
                    if (std::is_same<float, T>::value)      value.append(item.toString().toFloat());
                    if (std::is_same<double, T>::value)     value.append(item.toString().toDouble());
                    if (std::is_same<bool, T>::value)       value.append(item.toBool());
                }
            }
        } else {
            value.clear();
        }
        return value;
    }
    return default_value;
}
// Getter: QList<QString>
template <typename T, typename std::enable_if<std::is_same<QString, T>::value, bool>::type = true>
QList<T> Get(const QJsonObject &obj, const QString &key, QList<T> default_value = QList<T>()) {
    if (obj.contains(key)) {
        QList<T> value;
        QJsonValue val = obj.value(key);
        if (val.isObject()) {
            value.append(val.toString());
        } else if (val.isArray()) {
            QJsonArray array = val.toArray();
            for (const QJsonValue &item : qAsConst(array)) {
                if (item.isObject()) {
                    value.append(item.toString());
                }
            }
        } else {
            value.clear();
        }
        return value;
    }
    return default_value;
}
// Getter: QHash<QString, T>
template <typename T, typename std::enable_if<std::is_convertible<T, QJsonValue>::value, bool>::type = true >
QHash<QString, T> Get(const QJsonObject &obj, const QString &key, QHash<QString, T> default_value = QHash<QString, T>()) {
    if (obj.contains(key)) {
        QHash<QString, T> value;
        QJsonValue val = obj.value(key);
        if (val.isObject()) {
            QJsonValue info(val.toObject());
            if (!info.isNull()) value.insert(key, info);
        } else if (val.isArray()) {
            QJsonArray array = val.toArray();
            for (const QJsonValue &item : qAsConst(array)) {
                if (item.isObject()) {
                    QJsonValue info(item.toObject());
                    if (!info.isNull()) value.insert(key, info);
                }
            }
        } else {
            value.clear();
        }
        return value;
    }
    return default_value;
}

// Setter: QJsonValue<T>
template <typename T, typename std::enable_if<std::is_convertible<T, QJsonValue>::value, bool>::type = true >
void Set(QJsonObject &obj, const QString &key, const T &value) {
    // if (!value.isEmpty()) {
    //     if (std::is_arithmetic<T>::value) {
    //         obj.insert(key, QString::number(value));
    //     } else {
    //         obj.insert(key, value);
    //     }
    // }
    obj.insert(key, value);
}
// Setter: QJsonValue<QVector<T>>
template <typename T, typename std::enable_if<std::is_convertible<T, QJsonValue>::value, bool>::type = true >
void Set(QJsonObject &obj, const QString &key, const QVector<T> &value) {
    QJsonArray array;
    for (const T &item : value) {
        // if (std::is_same<QString, T>::value) {
        //     if (!item.isEmpty()) array.append(item);
        // } else {
        //     QJsonValue value(item);
        //     if (!value.isNull()) array.append(value);
        // }
        QJsonValue value(item);
        if (!value.isNull()) array.append(value);
    }
    Set(obj, key, array);
}
// Setter: QJsonValue<QHash<QString, T>>
template <typename T, typename std::enable_if<std::is_convertible<T, QJsonValue>::value, bool>::type = true >
void Set(QJsonObject &obj, const QString &key, const QHash<QString, T> &value) {
    QJsonArray array;
    for (const T &item : value) {
        // if (std::is_same<QString, T>::value) {
        //     if (!item.isEmpty()) array.append(item);
        // } else {
        //     QJsonValue value(item);
        //     if (!value.isNull()) array.append(value);
        // }
        QJsonValue value(item);
        if (!value.isNull()) array.append(value);
    }
    Set(obj, key, array);
}

// Dumper
void Dumper(const QJsonObject &json_object, const QString &header = QString(), const QString &prefix = QString());
void Dumper(const QJsonArray &json_array, const QString &header = QString(), const QString &prefix = QString());
} // JsonUtils

namespace StringUtils {
QTextCodec *GetTextCodec(const QString &codec_name);
QString Convert(const uchar *u_str, int length);
QString Convert(const QByteArray &src);
QByteArray ConvertFrom(const QString &src, const QString &codec_name = QLatin1String("System"));
QByteArray ConvertTo(const QString &src, const QString &code_name = QLatin1String("System"));
QString toHexString(const QString &src, const QString &codec_name = QLatin1String("System"));
QString fromHexString(const QString &src, const QString &codec_name = QLatin1String("System"));
wchar_t *toWCharArray(const char *str, int str_len = -1);
char* toCharArray(const wchar_t *wstr, int wstr_len = -1);
size_t toWString(const std::string &src, std::wstring &dst);
int toString(const std::wstring &src, std::string &dst);
} // StringUtils

namespace ListUtils {
// QList<QString> --> QList<int>
QList<int> Convert(const QList<QString> &src);
// QList<int> --> QList<QString>
QList<QString> Convert(const QList<int> &src);
// QList<QString> --> QList<QVariant>
QList<QVariant> toVariants(const QList<QString> &src);
// QList<int> --> QList<QVariant>
QList<QVariant> toVariants(const QList<int> &src);
} // ListUtils

namespace MiscUtils {
// HightDPI
void EnableHightDPI();
// Sleep
void m_sleep(quint32 ms);
// Signal Handler
void ExitAfter(qint32 msec);
void HandelSignal(qint32 signal_number, SignalHandlerCallback callback);
// Url Validator
bool isValidUrl(QString url);
bool LoadLanguage(const QString &filename = QString(), const QString &filepath = QString(), const QString &language = QLocale::system().name());
// Symbol Link Reader
QString RealPath(const QString &file, int max_depth = 10);
} // MiscUtils

namespace WinUtils {
#if defined(Q_OS_WIN) || defined(Q_CC_MINGW)
// Register
QVariant QueryRegister(HKEY hRoot, const QString &Key, const QString &ValueName, DWORD *type);
// Console
bool AttachOutputToConsole(void);
// DesktopUI
void DesktopAndTaskBarSetVisible(bool visible);
// Privileges
bool RunAsAdmin();
bool RequestPrivileges();
bool ReleasePrivileges();
#endif
} // WinUtils
